/*
 * @(#)CIHashtable.java   0.3-3 06/05/2001
 *
 *  This file is part of the HTTPClient package
 *  Copyright (C) 1996-2001 Ronald Tschal�r
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA 02111-1307, USA
 *
 *  For questions, suggestions, bug-reports, enhancement-requests etc.
 *  I may be contacted at:
 *
 *  ronald@innovation.ch
 *
 *  The HTTPClient's home page is located at:
 *
 *  http://www.innovation.ch/java/HTTPClient/ 
 *
 */

package org.everrest.http.client;

import java.util.Enumeration;
import java.util.Hashtable;

/**
 * This class implements a Hashtable with case-insensitive Strings as keys.
 *
 * @author Ronald Tschal�r
 * @version 0.3-3 06/05/2001
 */
class CIHashtable extends Hashtable {
    // Constructors

    /**
     * Create a new CIHashtable with the specified initial capacity and the
     * specified load factor.
     *
     * @param intialCapacity
     *         the initial number of buckets
     * @param loadFactor
     *         a number between 0.0 and 1.0
     * @see java.util.Hashtable(int, float)
     */
    public CIHashtable(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
    }

    /**
     * Create a new CIHashtable with the specified initial capacity.
     *
     * @param intialCapacity
     *         the initial number of buckets
     * @see java.util.Hashtable(int)
     */
    public CIHashtable(int initialCapacity) {
        super(initialCapacity);
    }

    /**
     * Create a new CIHashtable with a default initial capacity.
     *
     * @see java.util.Hashtable()
     */
    public CIHashtable() {
        super();
    }

    // Methods

    /**
     * Retrieves the object associated with the specified key. The key lookup is
     * case-insensitive.
     *
     * @param key
     *         the key
     * @return the object associated with the key, or null if none found.
     * @see java.util.Hashtable.get(Object)
     */
    public Object get(String key) {
        return super.get(new CIString(key));
    }

    /**
     * Stores the specified object with the specified key.
     *
     * @param key
     *         the key
     * @param value
     *         the object to be associated with the key
     * @return the object previously associated with the key, or null if there
     *         was none.
     * @see java.util.Hashtable.put(Object, Object)
     */
    public Object put(String key, Object value) {
        return super.put(new CIString(key), value);
    }

    /**
     * Looks whether any object is associated with the specified key. The key
     * lookup is case insensitive.
     *
     * @param key
     *         the key
     * @return true is there is an object associated with key, false otherwise
     * @see java.util.Hashtable.containsKey(Object)
     */
    public boolean containsKey(String key) {
        return super.containsKey(new CIString(key));
    }

    /**
     * Removes the object associated with this key from the Hashtable. The key
     * lookup is case insensitive.
     *
     * @param key
     *         the key
     * @return the object associated with this key, or null if there was none.
     * @see java.util.Hashtable.remove(Object)
     */
    public Object remove(String key) {
        return super.remove(new CIString(key));
    }

    /**
     * Returns an enumeration of all the keys in the Hashtable.
     *
     * @return the requested Enumerator
     * @see java.util.Hashtable.keys(Object)
     */
    public Enumeration keys() {
        return new CIHashtableEnumeration(super.keys());
    }
}

/**
 * A simple enumerator which delegates everything to the real enumerator. If a
 * CIString element is returned, then the string it represents is returned
 * instead.
 */
final class CIHashtableEnumeration implements Enumeration {
    Enumeration HTEnum;

    public CIHashtableEnumeration(Enumeration en) {
        HTEnum = en;
    }

    public boolean hasMoreElements() {
        return HTEnum.hasMoreElements();
    }

    public Object nextElement() {
        Object tmp = HTEnum.nextElement();
        if (tmp instanceof CIString)
            return ((CIString)tmp).getString();

        return tmp;
    }
}

/**
 * This class' raison d'etre is that I want to use a Hashtable using Strings as
 * keys and I want the lookup be case insensitive, but I also want to be able
 * retrieve the keys with original case (otherwise I could just use
 * toLowerCase() in the get() and put()). Since the class String is final we
 * create a new class that holds the string and overrides the methods hashCode()
 * and equals().
 */
final class CIString {
    /** the string */
    private String string;

    /** the hash code */
    private int hash;

    /** the constructor */
    public CIString(String string) {
        this.string = string;
        this.hash = calcHashCode(string);
    }

    /** return the original string */
    public final String getString() {
        return string;
    }

    /** the hash code was precomputed */
    public int hashCode() {
        return hash;
    }

    /**
     * We smash case before calculation so that the hash code is "case
     * insensitive". This is based on code snarfed from
     * java.lang.String.hashCode().
     */
    private static final int calcHashCode(String str) {
        int hash = 0;
        char llc[] = lc;
        int len = str.length();

        for (int idx = 0; idx < len; idx++)
            hash = 31 * hash + llc[str.charAt(idx)];

        return hash;
    }

    /** Uses the case insensitive comparison. */
    public boolean equals(Object obj) {
        if (obj != null) {
            if (obj instanceof CIString)
                return string.equalsIgnoreCase(((CIString)obj).string);

            if (obj instanceof String)
                return string.equalsIgnoreCase((String)obj);
        }

        return false;
    }

    /** Just return the internal string. */
    public String toString() {
        return string;
    }

    private static final char[] lc = new char[256];

    static {
        // just ISO-8859-1
        for (char idx = 0; idx < 256; idx++)
            lc[idx] = Character.toLowerCase(idx);
    }
}
